#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include<string.h>
#include "font.h"
#include "framebuffer_output.h"

//C语言的多态化,方便绘制不同大小的字体
typedef void (*put_char)(int,/*x坐标*/
                          int,/*y坐标*/
                          int,/*字符在字模数组中的序号*/
                          int/*颜色序号*/);

#define XORMODE	0x80000000//暂未知作用

union multiptr {//根据每个像素占用字节的多少而自由变化
    unsigned  char *p8;
    unsigned short *p16;
    unsigned  long *p32;
};

struct fb_info//framebuffer信息结构体,是我需要的
{
   int fb_fd;//文件描述符
   void *fb_mem;//framebuffer的内存映射
   unsigned int fb_xres;//framebuffer可视区域的宽度
   unsigned int fb_yres;//framebuffer可视区域的高度
   unsigned int fb_line_len;//一行占用多少字节(内存映射)
   unsigned int fb_size;//framebuffer内存映射占用内存的大小
   unsigned int fb_bpp;//内存中每个像素占用的位数
};

static int bytes_per_pixel;//每个像素占用的字节数
static struct fb_info fb_info;//framebuffer信息结构体
static struct fb_var_screeninfo vinfo;//framebuffer可变信息
static struct fb_fix_screeninfo finfo;//framebuffer固定信息
static unsigned char **line_addr = NULL;//每一行所在地址的指针

static unsigned colormap [256];//颜色表,最多可以有256种颜色
static int palette [] = {
    0x000000, 0xffe080, 0xffffff, 0xe0c0a0, 0x304050, 0x80b8c0
};//可以填入各种颜色,自己添加,对应colormap中的序号
#define NR_COLORS (sizeof (palette) / sizeof (palette [0]))

static int s_font_size = FONT_8X8;//设置一种默认的字模大小
typedef struct{
    int font_size;
    int width;
    int height;
} font_info_t;
static font_info_t font_arr[] = {
    {FONT_8X8,8,8}
    , {FONT_8X16,8,16}
};
#define NUM_FONT (sizeof (font_arr) / sizeof (font_arr [0]))
static unsigned char s_font_width = 8;
static unsigned char s_font_height = 8;

/*按自己需要设置颜色序列
  @param unsigned colidx 颜色序号
  @param unsigned value 颜色值
*/
void setcolor(unsigned colidx, unsigned value);

/*绘制指定坐标的 像素点
  @param int x x坐标
  @param int y y坐标
  @param unsigned colidx 颜色序号
*/
void pixel (int x, int y, unsigned colidx);

/*绘制8X8的字模
  @param int x x坐标
  @param int y y坐标
  @param unsigned colidx 颜色序号
*/
void put_char_8x8(int x, int y, int c, int colidx);

/*绘制8X16的字模
  @param int x x坐标
  @param int y y坐标
  @param unsigned colidx 颜色序号
*/
void put_char_8x16(int x, int y, int c, int colidx);

/*绘制16X16的字模
  @param int x x坐标
  @param int y y坐标
  @param unsigned colidx 颜色序号
*/
void put_char_16x16(int x, int y, int c, int colidx);

/*根据字模大小,返回绘制字模的函数
  @param int font_size 字模大小
*/
put_char get_put_char();

typedef struct{
    int font_size;
    put_char put_char_fun;
}put_char_t;

const static put_char_t put_char_arr[] = {
    {FONT_8X8,put_char_8x8}
    , {FONT_8X16,put_char_8x16}
};
#define NUM_PUT_CHAR (sizeof (put_char_arr) / sizeof (put_char_arr [0]))

int fb_open(char *pathname,screen_info_t* scr_info)
{
    //打开文件
    fb_info.fb_fd = open(pathname,O_RDWR);

    if(!fb_info.fb_fd)
    {
        printf("Error:cannot open /dev/fb0.\n");
        return -1;
    }

    //取得framebuffer的固定信息
    if(ioctl(fb_info.fb_fd, FBIOGET_FSCREENINFO, &finfo) < 0)
    {
        printf("Error:reading fixed info.\n");
        close(fb_info.fb_fd);
        return -1;
    }
    //取得framebuffer的可变信息
    if(ioctl(fb_info.fb_fd, FBIOGET_VSCREENINFO, &vinfo))
    {
        printf("Error reading var info.\n");
        close(fb_info.fb_fd);
        return -1;
    }

    fb_info.fb_xres = vinfo.xres;
    fb_info.fb_yres = vinfo.yres;
    fb_info.fb_line_len = finfo.line_length;
    fb_info.fb_bpp = vinfo.bits_per_pixel;
    fb_info.fb_size = finfo.smem_len;
    bytes_per_pixel = (fb_info.fb_bpp + 7) / 8;

    if (scr_info)
    {
        scr_info->screen_width = fb_info.fb_xres;
        scr_info->screen_height = fb_info.fb_yres;
    }

    //映射
    fb_info.fb_mem = mmap(0, fb_info.fb_size, PROT_READ | PROT_WRITE, MAP_FILE | MAP_SHARED, fb_info.fb_fd, 0);
    if (fb_info.fb_mem == MAP_FAILED) {
        fprintf(stderr, "Error: failed to map framebuffer device to memory");
        exit(1);
    }

    //memset(fb_info.fb_mem,0,fb_info.fb_size);

    line_addr = (unsigned char **)malloc (sizeof (__u32) * vinfo.yres_virtual);
    if (!line_addr)
        return -1;
    unsigned y, addr;
    addr = 0;
    for (y = 0; y < vinfo.yres_virtual; y++, addr += finfo.line_length)
        line_addr [y] = (unsigned char*)fb_info.fb_mem + addr;

    int i;
    //设置颜色序列
    for (i = 0; i < NR_COLORS; i++)
        setcolor (i, palette [i]);

    return 0;
}

int fb_close()
{
   if(0!=munmap(fb_info.fb_mem,  fb_info.fb_size))
   {
      printf("Error:munmap was fault.\n");
      return -1;
   }
   if(0!=close(fb_info.fb_fd))
   {
      printf("Error:close file was fault.\n");
      return -1;
   }
   if (!line_addr)
   {
      free(line_addr);
      line_addr = NULL;
   }
   return 0;
}

void fb_clean()
{
    memset(fb_info.fb_mem,0,fb_info.fb_size);
}

void setcolor(unsigned colidx, unsigned value)
{
    /*其中内容暂未明白*/
    unsigned res;
    unsigned short red, green, blue;
    struct fb_cmap cmap;

#ifdef DEBUG
    if (colidx > 255) {
        fprintf (stderr, "WARNING: color index = %u, must be <256\n",
                 colidx);
        return;
    }
#endif

    switch (bytes_per_pixel) {
    default:
    case 1:
        res = colidx;
        red = (value >> 8) & 0xff00;
        green = value & 0xff00;
        blue = (value << 8) & 0xff00;
        cmap.start = colidx;
        cmap.len = 1;
        cmap.red = &red;
        cmap.green = &green;
        cmap.blue = &blue;
        cmap.transp = NULL;

        if (ioctl (fb_info.fb_fd, FBIOPUTCMAP, &cmap) < 0)
            perror("ioctl FBIOPUTCMAP");
        break;
    case 2:
    case 4:
        red = (value >> 16) & 0xff;
        green = (value >> 8) & 0xff;
        blue = value & 0xff;
        res = ((red >> (8 - vinfo.red.length)) << vinfo.red.offset) |
            ((green >> (8 - vinfo.green.length)) << vinfo.green.offset) |
            ((blue >> (8 - vinfo.blue.length)) << vinfo.blue.offset);
    }
    colormap [colidx] = res;
}

/*绘制framebuffer像素点
  @param union multiptr loc 像素点
  @param unsigned xormode 模式
  @param unsigned color 颜色
*/
static inline void __setpixel (union multiptr loc, unsigned xormode, unsigned color)
{
    switch(bytes_per_pixel) {
    case 1:
    default:
        if (xormode)
            *loc.p8 ^= color;
        else
            *loc.p8 = color;
        break;
    case 2:
        if (xormode)
            *loc.p16 ^= color;
        else
            *loc.p16 = color;
        break;
    case 4:
        if (xormode)
            *loc.p32 ^= color;
        else
            *loc.p32 = color;
        break;
    }
}

void pixel (int x, int y, unsigned colidx)
{
    unsigned xormode;
    union multiptr loc;

    if ((x < 0) || ((__u32)x >= vinfo.xres_virtual) ||
        (y < 0) || ((__u32)y >= vinfo.yres_virtual))
        return;

    xormode = colidx & XORMODE;
    colidx &= ~XORMODE;

#ifdef DEBUG
    if (colidx > 255) {
        fprintf (stderr, "WARNING: color value = %u, must be <256\n",
             colidx);
        return;
    }
#endif

    loc.p8 = line_addr [y] + x * bytes_per_pixel;
    __setpixel (loc, xormode, colormap [colidx]);
}

void put_char_8x8(int x, int y, int c, int colidx)
{
    int i,j,bits;
    for (i = 0; i < font_vga_8x8.height; i++) {
        bits = font_vga_8x8.data [font_vga_8x8.height * c + i];
        for (j = 0; j < font_vga_8x8.width; j++, bits <<= 1)
        {
            if (bits & 0x80)
                pixel (x + j, y + i, colidx);
        }
    }
}

void put_char_8x16(int x, int y, int c, int colidx)
{
    int i,j,bits;
    for (i = 0; i < font_vga_8x16.height; i++) {
        bits = font_vga_8x16.data [font_vga_8x16.height * c + i];
        for (j = 0; j < font_vga_8x16.width; j++, bits <<= 1)
        {
            if (bits & 0x80)
                pixel (x + j, y + i, colidx);
        }
    }
}

#if 0
void put_char_16x16(int x, int y, int c, int colidx)
{
    int i,j,bits;
    for (i = 0; i < font_vga_16x16.height; i++) {
        bits = font_vga_16x16.data [font_vga_16x16.height * c*2 + i*2];
        for (j = 0; j < 8; j++, bits <<= 1)
        {
            if (bits & 0x80)
                pixel (x + j, y + i, colidx);
        }
        bits = font_vga_16x16.data [font_vga_16x16.height * c* 2 + i*2 + 1];
        for (j = 8; j < font_vga_16x16.width; j++, bits <<= 1)
        {
            if (bits & 0x80)
                pixel (x + j, y + i, colidx);
        }
    }
}
#endif

/*获取对应字体的绘制字模函数
  @param int font_size 字模大小
  @param put_char 函数指针,如果未指定则返回NULL
*/
put_char get_put_char()
{
    int i;
    for (i = 0; i < NUM_PUT_CHAR; i++)
    {
        if (put_char_arr[i].font_size == s_font_size)
            return put_char_arr[i].put_char_fun;
    }

    return NULL;
}

void put_string(int x, int y, char *s, unsigned colidx)
{
    put_char fun = get_put_char();
    if (!fun) {
        fprintf(stderr, "%d:%s",__LINE__,__FILE__);
        exit(1);
    }
    while (*s) {
        fun(x,y,*s,colidx);
        ++s;
        x += s_font_width;
    }
}

void put_string_center(int x, int y, char *s, unsigned colidx)
{
    size_t sl = strlen (s);
    put_string (x - (sl / 2) * s_font_width,
                    y - s_font_height / 2, s, colidx);
}

void set_font(int font_size)
{
    int i;
    for (i = 0; i < NUM_FONT; i++)
    {
        if (font_arr[i].font_size == font_size)
        {
            s_font_size = font_size;
            s_font_width = font_arr[i].width;
            s_font_height = font_arr[i].height;
            break;
        }
    }
}

void line (int x1, int y1, int x2, int y2, unsigned colidx)
{
    int tmp;
    int dx = x2 - x1;
    int dy = y2 - y1;

    if (abs (dx) < abs (dy)) {
        if (y1 > y2) {
            tmp = x1; x1 = x2; x2 = tmp;
            tmp = y1; y1 = y2; y2 = tmp;
            dx = -dx; dy = -dy;
        }
        x1 <<= 16;
        /* dy is apriori >0 */
        dx = (dx << 16) / dy;
        while (y1 <= y2) {
            pixel (x1 >> 16, y1, colidx);
            x1 += dx;
            y1++;
        }
    } else {
        if (x1 > x2) {
            tmp = x1; x1 = x2; x2 = tmp;
            tmp = y1; y1 = y2; y2 = tmp;
            dx = -dx; dy = -dy;
        }
        y1 <<= 16;
        dy = dx ? (dy << 16) / dx : 0;
        while (x1 <= x2) {
            pixel (x1, y1 >> 16, colidx);
            y1 += dy;
            x1++;
        }
    }
}

void rect (int x1, int y1, int x2, int y2, unsigned colidx)
{
    line (x1, y1, x2, y1, colidx);
    line (x2, y1, x2, y2, colidx);
    line (x2, y2, x1, y2, colidx);
    line (x1, y2, x1, y1, colidx);
}

void fillrect (int x1, int y1, int x2, int y2, unsigned colidx)
{
    int tmp;
    unsigned xormode;
    union multiptr loc;

    /* Clipping and sanity checking */
    if (x1 > x2) { tmp = x1; x1 = x2; x2 = tmp; }
    if (y1 > y2) { tmp = y1; y1 = y2; y2 = tmp; }
    if (x1 < 0) x1 = 0; if ((__u32)x1 >= fb_info.fb_xres) x1 = fb_info.fb_xres - 1;
    if (x2 < 0) x2 = 0; if ((__u32)x2 >= fb_info.fb_xres) x2 = fb_info.fb_xres - 1;
    if (y1 < 0) y1 = 0; if ((__u32)y1 >= fb_info.fb_yres) y1 = fb_info.fb_yres - 1;
    if (y2 < 0) y2 = 0; if ((__u32)y2 >= fb_info.fb_yres) y2 = fb_info.fb_yres - 1;

    if ((x1 > x2) || (y1 > y2))
        return;

    xormode = colidx & XORMODE;
    colidx &= ~XORMODE;

#ifdef DEBUG
    if (colidx > 255) {
        fprintf (stderr, "WARNING: color value = %u, must be <256\n",
             colidx);
        return;
    }
#endif

    colidx = colormap [colidx];

    for (; y1 <= y2; y1++) {
        loc.p8 = line_addr [y1] + x1 * bytes_per_pixel;
        for (tmp = x1; tmp <= x2; tmp++) {
            __setpixel (loc, xormode, colidx);
            loc.p8 += bytes_per_pixel;
        }
    }
}

void button_draw (struct ts_button *button)
{
    rect (button->x, button->y, button->x + button->w - 1,
          button->y + button->h - 1, button->rect_colidx);
    fillrect (button->x + 1, button->y + 1,
          button->x + button->w - 2,
          button->y + button->h - 2, button->fillrect_colidx);
    put_string_center (button->x + button->w / 2,
               button->y + button->h / 2,
               button->text, button->string_colidx);
}

int main(int argc, char* const argv[])
{
    screen_info_t scr_info;
    fb_open("/dev/fb0",&scr_info);

    //fb_clean();
    memset(fb_info.fb_mem,0,fb_info.fb_size);

    set_font(FONT_8X16);
    put_string(scr_info.screen_width/2,scr_info.screen_height/2, argv[1], 3);

    //line(0, 80, 670, 80, 6);
    //line(670, 0, 670, 480, 6);
    return 0;
}

